package com.nvwa.coding.model;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.AtomicReference;

import static com.nvwa.coding.util.safe.Checker.isBlank;
import static com.nvwa.coding.util.safe.Safer.*;
import static com.nvwa.coding.util.safe.Transformer.longOrNull;


/**
 * <h2>以字符串形式存储的日期时间数据</h2>
 * <p>支持通过构造方法{@link #StringDateTime(String)}自动解析各种合理的日期时间格式</p>
 * <p>已支持json序列化</p>
 * <p>如果传入字符串的格式无法解析，不会报错，而是会正常当做字符串使用：</p>
 * <p>{@link #toString()}、{@link #asString()}、{@link #getOrigin()}</p>
 * <p>但是涉及日期时间计算或转换的，结果会为null或返回一个代表空字符串的实例：</p>
 * <p>{@link #asLocalDateTime()}、{@link #to_yyyyMMddHHmmss()}、{@link #withDtf(DateTimeFormatter)}、{@link #withFormat(String)}</p>
 *
 */
public class StringDateTime implements ValueValidator {

    /**
     * @deprecated 这个dtf是假的，时间戳缺少时区信息，并不能直接作为Dtf使用，这个值只是为了作为标识对时间戳进行特殊处理
     */
    @Deprecated
    public static final DateTimeFormatter dtf_ts = DateTimeFormatter.ofPattern(""); // 13
    public static final DateTimeFormatter dtf_yyyyMMdd = DateTimeFormatter.ofPattern("yyyyMMdd"); // 8
    public static final DateTimeFormatter dtf_yyyyMMdd_A = DateTimeFormatter.ofPattern("yyyy-MM-dd"); // 10
    public static final DateTimeFormatter dtf_yyyyMMdd_B = DateTimeFormatter.ofPattern("yyyy/MM/dd"); // 10
    public static final DateTimeFormatter dtf_yyyyMMddHHmm = DateTimeFormatter.ofPattern("yyyyMMddHHmm"); // 12
    public static final DateTimeFormatter dtf_yyyyMMddHHmm_A = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); // 16
    public static final DateTimeFormatter dtf_yyyyMMddHHmm_B = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm"); // 16
    public static final DateTimeFormatter dtf_yyyyMMddHHmmss = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); // 14
    public static final DateTimeFormatter dtf_yyyyMMddHHmmss_A = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // 19
    public static final DateTimeFormatter dtf_yyyyMMddHHmmss_B = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); // 19
    public static final DateTimeFormatter dtf_yyyyMMddHHmmssSSS = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); //17
    public static final DateTimeFormatter dtf_yyyyMMddHHmmss0SSS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss.SSS"); // 18
    public static final DateTimeFormatter dtf_yyyyMMddHHmmss0SSS_A = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); // 23
    public static final DateTimeFormatter dtf_yyyyMMddHHmmss0SSS_B = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS"); // 23

    @Getter
    protected final String origin;

    @JsonIgnore
    protected final DateTimeFormatter originFormatter;

    @JsonIgnore
    protected AtomicReference<LocalDateTime> cache = new AtomicReference<>();

    @JsonCreator
    public StringDateTime(String origin) {
        this.origin = origin;
        this.originFormatter = parseDateTimeFormatter(origin);
    }

    public StringDateTime(String origin, DateTimeFormatter originFormatter) {
        this.origin = origin;
        this.originFormatter = originFormatter;
    }

    public StringDateTime(LocalDateTime localDateTime, DateTimeFormatter dateTimeFormatter) {
        this.origin = format(localDateTime, dateTimeFormatter);
        this.originFormatter = dateTimeFormatter;
        this.cache.set(localDateTime);
    }

    protected DateTimeFormatter parseDateTimeFormatter(String s) {
        switch (length(s)) {
            case 8:
                return dtf_yyyyMMdd;
            case 10:
                if (s.contains("-")) {
                    return dtf_yyyyMMdd_A;
                }
                if (s.contains("/")) {
                    return dtf_yyyyMMdd_B;
                }
            case 12:
                return dtf_yyyyMMddHHmm;
            case 13:
                // 13位，并且可以正确转为数字，姑且认为是时间戳，够用好几百年了
                if (silentGetOpt(() -> Long.valueOf(s)).isPresent()) {
                    return dtf_ts;
                }
            case 14:
                return dtf_yyyyMMddHHmmss;
            case 17:
                return dtf_yyyyMMddHHmmssSSS;
            case 18:
                return dtf_yyyyMMddHHmmss0SSS;
            case 19:
                if (s.contains("-")) {
                    return dtf_yyyyMMddHHmmss_A;
                }
                if (s.contains("/")) {
                    return dtf_yyyyMMddHHmmss_B;
                }
            case 23:
                if (s.contains("-")) {
                    return dtf_yyyyMMddHHmmss0SSS_A;
                }
                if (s.contains("/")) {
                    return dtf_yyyyMMddHHmmss0SSS_B;
                }
            default:
                return null;
        }
    }

    public LocalDateTime asLocalDateTime() {
        if (isBlank(origin) || originFormatter == null || cache == null) {
            return null;
        }
        if (cache.get() != null) {
            return cache.get();
        }
        LocalDateTime localDateTime = loggingGetOrNull(() -> parse(origin, originFormatter));
        if (localDateTime == null) {
            cache = null;
            return null;
        }
        cache.set(localDateTime);
        return localDateTime;
    }

    /**
     * 使用的时区，不合适可以在子类修改
     */
    protected ZoneOffset zoneOffset() {
        return ZoneOffset.ofHours(8);
    }

    /**
     * 定义解析字符串的规则
     *
     * @param s   字符串
     * @param dtf 使用的解析格式
     * @return 解析后的日期时间对象
     */
    protected LocalDateTime parse(String s, DateTimeFormatter dtf) {
        if (isBlank(s)) {
            return null;
        }
        // 时间戳格式特殊处理
        if (dtf == dtf_ts) {
            return parseTs(longOrNull(s));
        }
        // 纯日期无法转换，补全时间
        if (dtf == dtf_yyyyMMdd) {
            return LocalDateTime.parse(new StringBuilder(s).append("000000"), dtf_yyyyMMddHHmmss);
        }
        if (dtf == dtf_yyyyMMdd_A) {
            return LocalDateTime.parse(new StringBuilder(s).append(" 00:00:00"), dtf_yyyyMMddHHmmss_A);
        }
        if (dtf == dtf_yyyyMMdd_B) {
            return LocalDateTime.parse(new StringBuilder(s).append(" 00:00:00"), dtf_yyyyMMddHHmmss_B);
        }
        // 不带点的毫秒格式无法直接转回LocalDateTime，需要以带点的格式转换
        if (dtf == dtf_yyyyMMddHHmmssSSS) {
            return LocalDateTime.parse(new StringBuilder(s).insert(s.length() - 3, "."), dtf_yyyyMMddHHmmss0SSS);
        }
        return LocalDateTime.parse(s, dtf);
    }

    /**
     * 定义序列化规则
     *
     * @param localDateTime 日期时间对象
     * @param dtf           使用的序列化格式
     * @return 字符串格式的日期时间
     */
    protected String format(LocalDateTime localDateTime, DateTimeFormatter dtf) {
        // ts格式要单独处理
        if (dtf == dtf_ts) {
            return String.valueOf(localDateTime.toInstant(zoneOffset()).toEpochMilli());
        }
        return localDateTime.format(dtf);
    }

    protected LocalDateTime parseTs(Long ts) {
        return ts != null ? Instant.ofEpochMilli(ts).atZone(zoneOffset()).toLocalDateTime() : null;
    }


    public static StringDateTime now_ts() {
        return StringDateTime.of_ts(System.currentTimeMillis());
    }

    public static StringDateTime now_yyyyMMdd() {
        return StringDateTime.of_yyyyMMdd(LocalDateTime.now());
    }

    public static StringDateTime now_yyyyMMdd_A() {
        return StringDateTime.of_yyyyMMdd_A(LocalDateTime.now());
    }

    public static StringDateTime now_yyyyMMdd_B() {
        return StringDateTime.of_yyyyMMdd_B(LocalDateTime.now());
    }

    public static StringDateTime now_yyyyMMddHHmm() {
        return StringDateTime.of_yyyyMMddHHmm(LocalDateTime.now());
    }

    public static StringDateTime now_yyyyMMddHHmm_A() {
        return StringDateTime.of_yyyyMMddHHmm_A(LocalDateTime.now());
    }

    public static StringDateTime now_yyyyMMddHHmm_B() {
        return StringDateTime.of_yyyyMMddHHmm_B(LocalDateTime.now());
    }

    public static StringDateTime now_yyyyMMddHHmmss() {
        return StringDateTime.of_yyyyMMddHHmmss(LocalDateTime.now());
    }

    public static StringDateTime now_yyyyMMddHHmmss_A() {
        return StringDateTime.of_yyyyMMddHHmmss_A(LocalDateTime.now());
    }

    public static StringDateTime now_yyyyMMddHHmmss_B() {
        return StringDateTime.of_yyyyMMddHHmmss_B(LocalDateTime.now());
    }

    public static StringDateTime now_yyyyMMddHHmmssSSS() {
        return StringDateTime.of_yyyyMMddHHmmssSSS(LocalDateTime.now());
    }

    public static StringDateTime now_yyyyMMddHHmmss0SSS() {
        return StringDateTime.of_yyyyMMddHHmmss0SSS(LocalDateTime.now());
    }

    public static StringDateTime now_yyyyMMddHHmmss0SSS_A() {
        return StringDateTime.of_yyyyMMddHHmmss0SSS_A(LocalDateTime.now());
    }

    public static StringDateTime now_yyyyMMddHHmmss0SSS_B() {
        return StringDateTime.of_yyyyMMddHHmmss0SSS_B(LocalDateTime.now());
    }


    /**
     * 接收时间戳(eg. {@link System#currentTimeMillis()})
     */
    public static StringDateTime of_ts(long ts) {
        return new StringDateTime(String.valueOf(ts), dtf_ts);
    }

    /**
     * 接收时间戳(eg. {@link System#currentTimeMillis()})
     */
    public static StringDateTime of_ts(String ts) {
        return new StringDateTime(ts, dtf_ts);
    }

    public static StringDateTime of_yyyyMMdd(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMdd);
    }

    public static StringDateTime of_yyyyMMdd_A(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMdd_A);
    }

    public static StringDateTime of_yyyyMMdd_B(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMdd_B);
    }

    public static StringDateTime of_yyyyMMddHHmm(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMddHHmm);
    }

    public static StringDateTime of_yyyyMMddHHmm_A(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMddHHmm_A);
    }

    public static StringDateTime of_yyyyMMddHHmm_B(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMddHHmm_B);
    }

    public static StringDateTime of_yyyyMMddHHmmss(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMddHHmmss);
    }

    public static StringDateTime of_yyyyMMddHHmmss_A(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMddHHmmss_A);
    }

    public static StringDateTime of_yyyyMMddHHmmss_B(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMddHHmmss_B);
    }

    public static StringDateTime of_yyyyMMddHHmmssSSS(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMddHHmmssSSS);
    }

    public static StringDateTime of_yyyyMMddHHmmss0SSS(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMddHHmmss0SSS);
    }

    public static StringDateTime of_yyyyMMddHHmmss0SSS_A(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMddHHmmss0SSS_A);
    }

    public static StringDateTime of_yyyyMMddHHmmss0SSS_B(LocalDateTime localDateTime) {
        return new StringDateTime(localDateTime, dtf_yyyyMMddHHmmss0SSS_B);
    }


    public StringDateTime withDtf(DateTimeFormatter dtf) {
        if (originFormatter == dtf) {
            return this;
        }
        LocalDateTime localDateTime = asLocalDateTime();
        if (localDateTime != null) {
            return new StringDateTime(localDateTime, dtf);
        }
        return new StringDateTime(origin, dtf);
    }

    public StringDateTime withFormat(String format) {
        return withDtf(DateTimeFormatter.ofPattern(format));
    }

    public StringDateTime to_ts() {
        return withDtf(dtf_ts);
    }

    public StringDateTime to_yyyyMMdd() {
        return withDtf(dtf_yyyyMMdd);
    }

    public StringDateTime to_yyyyMMdd_A() {
        return withDtf(dtf_yyyyMMdd_A);
    }

    public StringDateTime to_yyyyMMdd_B() {
        return withDtf(dtf_yyyyMMdd_B);
    }

    public StringDateTime to_yyyyMMddHHmm() {
        return withDtf(dtf_yyyyMMddHHmm);
    }

    public StringDateTime to_yyyyMMddHHmm_A() {
        return withDtf(dtf_yyyyMMddHHmm_A);
    }

    public StringDateTime to_yyyyMMddHHmm_B() {
        return withDtf(dtf_yyyyMMddHHmm_B);
    }

    public StringDateTime to_yyyyMMddHHmmss() {
        return withDtf(dtf_yyyyMMddHHmmss);
    }

    public StringDateTime to_yyyyMMddHHmmss_A() {
        return withDtf(dtf_yyyyMMddHHmmss_A);
    }

    public StringDateTime to_yyyyMMddHHmmss_B() {
        return withDtf(dtf_yyyyMMddHHmmss_B);
    }

    public StringDateTime to_yyyyMMddHHmmssSSS() {
        return withDtf(dtf_yyyyMMddHHmmssSSS);
    }

    public StringDateTime to_yyyyMMddHHmmss0SSS() {
        return withDtf(dtf_yyyyMMddHHmmss0SSS);
    }

    public StringDateTime to_yyyyMMddHHmmss0SSS_A() {
        return withDtf(dtf_yyyyMMddHHmmss0SSS_A);
    }

    public StringDateTime to_yyyyMMddHHmmss0SSS_B() {
        return withDtf(dtf_yyyyMMddHHmmss0SSS_B);
    }



    @Override
    public String toString() {
        return origin;
    }

    @JsonValue
    public String asString() {
        return origin;
    }

    @Override
    public boolean valid() {
        return asLocalDateTime() != null;
    }

    public StringTime onlyTime() {
        LocalDateTime localDateTime = asLocalDateTime();
        if (localDateTime != null) {
            return new StringTime(localDateTime.toLocalTime(), StringTime.dtf_HH_mm_ss);
        }
        return new StringTime(null);
    }

}
